package io.hellsinger.vortex.ui.component

import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Paint.FontMetricsInt
import android.widget.EdgeEffect
import kotlin.math.max
import kotlin.math.min

internal class StorageTextEditorRenderer(
    internal val target: StorageTextEditorView,
) {
    private val textPaint =
        Paint(Paint.DITHER_FLAG or Paint.ANTI_ALIAS_FLAG).apply {
            textSize = target.sp(16F)
            strokeWidth = target.dp(2F)
        }
    private val textFontMetrics: FontMetricsInt = textPaint.fontMetricsInt

    private val lineNumberPaint =
        Paint(Paint.DITHER_FLAG or Paint.ANTI_ALIAS_FLAG).apply {
            textSize = target.sp(16F)
            strokeWidth = target.dp(2F)
        }

    private val scrollBarPaint =
        Paint(Paint.DITHER_FLAG or Paint.ANTI_ALIAS_FLAG).apply {
            color = 0xFF00E676.toInt()
            strokeCap = Paint.Cap.ROUND
            strokeWidth = target.dp(2F)
        }

    private val horizontalScrollBarCornerRadius = target.dp(16F)
    private val verticalScrollBarCornerRadius = target.dp(16F)

    private val selectedColor = 0x5200E676

    private var lineCountWidth: Float = 0F

    private val topEdgeEffect = EdgeEffect(target.context).apply { color = 0xFF00E676.toInt() }
    private val bottomEdgeEffect = EdgeEffect(target.context).apply { color = 0xFF00E676.toInt() }
    private val leftEdgeEffect = EdgeEffect(target.context).apply { color = 0xFF00E676.toInt() }
    private val rightEdgeEffect = EdgeEffect(target.context).apply { color = 0xFF00E676.toInt() }

    var lineHeight = textFontMetrics.descent - textFontMetrics.ascent
        private set

    val isTopEdgeEffectFinished: Boolean
        get() = topEdgeEffect.isFinished
    val isBottomEdgeEffectFinished: Boolean
        get() = bottomEdgeEffect.isFinished

    val isVerticalEdgeEffectFinished
        get() = isTopEdgeEffectFinished || isBottomEdgeEffectFinished

    val isLeftEdgeEffectFinished: Boolean
        get() = leftEdgeEffect.isFinished
    val isRightEdgeEffectFinished: Boolean
        get() = rightEdgeEffect.isFinished

    val isHorizontalEffectFinished: Boolean
        get() = isLeftEdgeEffectFinished || isRightEdgeEffectFinished

    fun measureLineCountWidth(lineCount: Int) {
        lineCountWidth = textPaint.measureText(lineCount.toString())
    }

    fun onDraw(canvas: Canvas) {
        val selectedColorAlpha = 0 // ColorUtils.setAlphaComponent(selectedColor.value, 85)
        if (target.lines.isEmpty()) return
        val width = target.width
        val height = target.height
        val horizontalScrollRange = target.computeHorizontalScrollRange()
        val verticalScrollRange = target.computeVerticalScrollRange()

        val firstVisibleLineIndex = target.firstVisibleLineIndex
        val lastVisibleLineIndex = target.lastVisibleLineIndex

        for (index in firstVisibleLineIndex..lastVisibleLineIndex) {
            val line = target.lines[index]
            val scaledLineHeight = lineHeight * (index + 1)
            val scaledLineHeightFloat = scaledLineHeight.toFloat()

            canvas.drawText(
                line,
                0,
                line.length,
                lineCountWidth + lineNumberPaint.strokeWidth,
                scaledLineHeightFloat,
                textPaint,
            )

            canvas.drawText(
                (index + 1).toString(),
                0F,
                scaledLineHeightFloat,
                lineNumberPaint,
            )
        }

        canvas.drawLine(
            lineCountWidth,
            (firstVisibleLineIndex * lineHeight).toFloat(),
            lineCountWidth,
            (lastVisibleLineIndex * (lineHeight + 1)).toFloat(),
            lineNumberPaint,
        )

        // Horizontal scroll bar
        canvas.drawRoundRect(
            target.computeHorizontalScrollOffset().toFloat(),
            height - target.dp(8F),
            target.computeHorizontalScrollOffset().toFloat() + target.dp(16F),
            height - target.dp(8F),
            verticalScrollBarCornerRadius,
            verticalScrollBarCornerRadius,
            scrollBarPaint,
        )

        // Vertical scroll bar
        canvas.drawRoundRect(
            width - target.dp(8F),
            target.computeVerticalScrollOffset().toFloat(),
            width - target.dp(4F),
            target.computeVerticalScrollOffset().toFloat() + (lineHeight * 2),
            verticalScrollBarCornerRadius,
            verticalScrollBarCornerRadius,
            scrollBarPaint,
        )

        drawEdgeEffects(canvas)
    }

    fun onPullScrollOffset(
        oldX: Int,
        oldY: Int,
        horizontalScrollDistance: Float,
        verticalScrollDistance: Float,
        horizontalDisplacement: Float,
        verticalDisplacement: Float,
    ) {
        val pulledX = (oldX + horizontalScrollDistance).toInt()
        val pulledY = (oldY + verticalScrollDistance).toInt()

        if (isTopOverScrolled(pulledY)) {
            topEdgeEffect.onPull(-verticalScrollDistance / target.height, horizontalDisplacement)
            if (!isBottomEdgeEffectFinished) bottomEdgeEffect.onRelease()
        } else if (isBottomOverScrolled(pulledY)) {
            bottomEdgeEffect.onPull(verticalScrollDistance / target.height, horizontalDisplacement)
            if (!isTopEdgeEffectFinished) topEdgeEffect.onRelease()
        }

        if (isLeftOverScrolled(pulledX)) {
            leftEdgeEffect.onPull(-horizontalScrollDistance / target.width, verticalDisplacement)
            if (!isRightEdgeEffectFinished) rightEdgeEffect.onRelease()
        } else if (isRightOverScrolled(pulledX)) {
            rightEdgeEffect.onPull(horizontalScrollDistance / target.width, verticalDisplacement)
            if (!isLeftEdgeEffectFinished) leftEdgeEffect.onRelease()
        }
    }

    private fun drawEdgeEffects(canvas: Canvas) {
        var needsInvalidate = false

        if (!isTopEdgeEffectFinished) {
            canvas.saveThenRestore {
                translate(0F, min(0, target.scrollY).toFloat())
                topEdgeEffect.setSize(target.width, target.height)
                needsInvalidate = topEdgeEffect.draw(canvas) or needsInvalidate
            }
        }

        if (!isBottomEdgeEffectFinished) {
            canvas.saveThenRestore {
                translate(
                    (-target.width).toFloat(),
                    (
                        max(
                            target.computeVerticalScrollRange(),
                            target.scrollY,
                        ) + target.height
                    ).toFloat(),
                )
                canvas.rotate(180F, target.width.toFloat(), 0F)
                bottomEdgeEffect.setSize(target.width, target.height)
                needsInvalidate = bottomEdgeEffect.draw(canvas) or needsInvalidate
            }
        }

        if (!isLeftEdgeEffectFinished) {
            canvas.saveThenRestore {
                rotate(270F)
                translate((-height).toFloat(), min(0F, target.scrollX.toFloat()))
                leftEdgeEffect.setSize(target.height, width)
                needsInvalidate = leftEdgeEffect.draw(canvas) or needsInvalidate
            }
        }

        if (!isRightEdgeEffectFinished) {
            canvas.saveThenRestore {
                rotate(90F)
                translate(
                    0F,
                    (
                        max(
                            target.computeHorizontalScrollRange(),
                            target.scrollX,
                        ) + target.height
                    ).toFloat(),
                )
                rightEdgeEffect.setSize(target.height, target.width)
                needsInvalidate = rightEdgeEffect.draw(canvas) or needsInvalidate
            }
        }

        if (needsInvalidate) target.postInvalidateOnAnimation()
    }

    private fun isTopOverScrolled(distance: Int): Boolean = distance < 0

    private fun isBottomOverScrolled(distance: Int): Boolean = distance > target.computeVerticalScrollRange()

    private fun isLeftOverScrolled(distance: Int): Boolean = distance < 0

    private fun isRightOverScrolled(distance: Int): Boolean = distance > target.computeHorizontalScrollRange()

    fun onAbsorbTopScrollOffset(velocity: Int) = topEdgeEffect.onAbsorb(velocity)

    fun onAbsorbBottomScrollOffset(velocity: Int) = bottomEdgeEffect.onAbsorb(velocity)

    fun onAbsorbLeftScrollOffset(velocity: Int) = leftEdgeEffect.onAbsorb(velocity)

    fun onAbsorbRightScrollOffset(velocity: Int) = rightEdgeEffect.onAbsorb(velocity)

    private inline fun Canvas.saveThenRestore(block: Canvas.() -> Unit) {
        val count = save()
        block()
        restoreToCount(count)
    }
}
